{%-import 'propname.jinja2' as helper-%}
{%-set className = resolver.cpp_resolve_namespace(ns)+Name%}
{%-set rapidjson = resolver.cpp_resolve_namespace(['rapidjson']) %}
{%macro NestedObjectNameOnly(name) -%}
    {%-if name|UpperCamelCase != Name|UpperCamelCase%}{{-name | UpperCamelCase-}}{%else%}{{name | UpperCamelCase}}Property{%endif-%}
{%-endmacro%}
{%macro NestedObjectName(name) -%}
    {{className}}::{{NestedObjectNameOnly(name)}}
{%-endmacro%}
{%-macro ObjectType(propName, propSchema) -%}
    {%-if '$ref' in propSchema -%}
        {{-loader.Reference(resolver, propSchema['$ref'])-}}
    {%-else-%}
        {{-NestedObjectName(propName)-}}
    {%-endif-%}
{%-endmacro%}
{%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset-%}

{%import 'loader.jinja2' as loader with context%}
{%-for propName, propSchema in schema.RequiredList() %}
{{loader.Class('cpp', resolver, [className], NestedObjectNameOnly(propName), propSchema) }}
{%-endfor%}
{%-for propName, propSchema in schema.UnRequiredList() %}
{{loader.Class('cpp', resolver, [className], NestedObjectNameOnly(propName), propSchema) }}
{%-endfor%}

{%if schema.RequiredList() | length > 0 %}
{{className}}::{{Name}}(
    {%-for propName, propSchema in schema.RequiredList()-%}
        const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}{%if not loop.last%}, {%endif%}
    {%-endfor%}){{' : '}}
    {%-for propName, propSchema in schema.RequiredList()-%}
        {{propName|privatize}}({{propName | camelCase}}){%if not loop.last%}, {%endif%}
    {%-endfor%}
{

}
{%else%}
{{className}}::{{Name}}()
{

}

{%-if schema['properties'] | length == 1 %}
{%-set propName = schema.PropertyKeys()[0] %}
{%-set propSchema = schema.PropertyValues()[0] %}
{{className}}::{{Name}}(const {{ObjectType(propName, propSchema)}}& {{propName | camelCase}}) : {{propName|privatize}}({{propName | camelCase}})
{

}
{%-endif%}
{%if 'type' in propSchema and propSchema['type'] in ['boolean', 'integer', 'number'] %}
{{className}}::{{Name}}({{ {"boolean":"bool", "integer":"int", "number":"double"}[propSchema.type] }} {{propName | camelCase}}) : {{Name}}({{ObjectType(propName, propSchema)}}({{propName | camelCase}}))
{

}
{%-elif 'type' in propSchema and propSchema['type'] == 'string'-%}
{{className}}::{{Name}}(const std::string& {{propName | camelCase}}) : {{Name}}({{ObjectType(propName, propSchema)}}::FromString({{propName | camelCase}}))
{

}
{%-endif%} {# propschema type #}
{%endif%}

{%-for propName, propSchema in schema.properties.items() %}
{%-if propName in schema.required%}

{{ObjectType(propName, propSchema)}} {{className}}::Get{{propName | UpperCamelCase}}() const
{
    return {{propName | privatize}};
}

{{className}}& {{className}}::Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value)
{
    {{propName | privatize}} = value;
    {{propName | privatize}}.SetHandle({{helper.ConstPropertyName(propName)}});
    return *this;
}
{%else%}
boost::optional<{{ObjectType(propName, propSchema)}}> {{className}}::Get{{propName | UpperCamelCase}}() const
{
    return {{propName | privatize}};
}

{{className}}& {{className}}::Set{{propName | UpperCamelCase}}(const {{ObjectType(propName, propSchema)}}& value)
{
    {{propName | privatize}} = value;
    {{propName | privatize}}->SetHandle({{helper.ConstPropertyName(propName)}});
    return *this;
}
{%endif%}
{%-endfor%}

{{className}} {{className}}::FromJson(const {{rapidjson}}Value& json)
{
    if (!(json.IsObject()))
    {
        throw {{exception}}("JSON wasn't an object");
    }
    {##}
    {{exception}}Collection exceptionCollection;
    {%for propName, propSchema in schema.RequiredList()-%}
    boost::optional<{{ObjectType(propName, propSchema)}}> optLocal{{propName | UpperCamelCase}};
    if (!json.HasMember({{helper.ConstPropertyName(propName)}}))
    {
        exceptionCollection.AddException({{exception}}("Property is missing", "{{propName}}"));
    }
    else
    {
        optLocal{{propName | UpperCamelCase}} = {{ObjectType(propName, propSchema)}}::FromJson(json[{{helper.ConstPropertyName(propName)}}]);
        optLocal{{propName | UpperCamelCase}}->SetHandle({{helper.ConstPropertyName(propName)}});
    }
    {%endfor%}
    boost::optional<{{className}}> optNewInstance;
    {%-if schema.RequiredList() %}
    if ({%for propName, propSchema in schema.RequiredList()%}optLocal{{propName | UpperCamelCase}}{%if not loop.last%} && {%endif%}{%endfor%})
    {
        optNewInstance = {{className}}({%for propName, propSchema in schema.RequiredList()%}*optLocal{{propName | UpperCamelCase}}{%if not loop.last%}, {%endif%}{%endfor%});
    }
    {%else%}
    // Yes I know this looks weird to create a boost::optional only to immediately instantiate it.
    // However, this is generated code, and there is a branch you don't see here where we conditionally instantiate it.
    optNewInstance = {{className}}();
    {%-endif%}
    {%-for propName, propSchema in schema.UnRequiredList()%}
    if (json.HasMember({{helper.ConstPropertyName(propName)}}))
    {
        try
        {
            auto local{{propName | UpperCamelCase}} = {{ObjectType(propName, propSchema)}}::FromJson(json[{{helper.ConstPropertyName(propName)}}]);
            local{{propName | UpperCamelCase}}.SetHandle({{helper.ConstPropertyName(propName)}});
            if (optNewInstance)
            {
                optNewInstance->Set{{propName | UpperCamelCase}}(local{{propName | UpperCamelCase}});
            }
        }
        catch (const {{exception}}& e)
        {
            exceptionCollection.AddException(e, {{helper.ConstPropertyName(propName)}});
        }
        catch (const {{exception}}Collection& ec)
        {
            exceptionCollection.AddException(ec, {{helper.ConstPropertyName(propName)}});
        }
        catch (const std::exception& e)
        {
            exceptionCollection.AddException({{exception}}(e), {{helper.ConstPropertyName(propName)}});
        }
    }
    {%-endfor%}
    if (exceptionCollection.IsExceptional() || !optNewInstance)
    {
        throw exceptionCollection;
    }
    return *optNewInstance;
}

void {{className}}::ToJson({{rapidjson}}Value& value, {{resolver.cpp_resolve_namespace(['rapidjson', 'Value'])}}AllocatorType& allocator) const
{
    if (!value.IsObject())
    {
        value.SetObject();
    }

    {%-for propName, propSchema in schema.properties.items() %}
    {%-if propName in schema.required%}
    {{rapidjson}}Value temp{{propName|UpperCamelCase}};
    {{propName|privatize}}.ToJson(temp{{propName|UpperCamelCase}}, allocator);
    value.AddMember({{rapidjson}}StringRef({{helper.ConstPropertyName(propName)}}), temp{{propName|UpperCamelCase}}, allocator);
    {%else%}
    if ({{propName|privatize}})
    {
        {{rapidjson}}Value temp{{propName|UpperCamelCase}};
        {{propName|privatize}}->ToJson(temp{{propName|UpperCamelCase}}, allocator);
        value.AddMember({{rapidjson}}StringRef({{helper.ConstPropertyName(propName)}}), temp{{propName|UpperCamelCase}}, allocator);
    }
    {%endif%}
    {%-endfor%}
}

void {{className}}::SetHandle(const std::string& handle)
{
    _handle = handle;
}

std::string {{className}}::GetHandle() const
{
    return _handle;
}